package com.afdor.rws.core.extension.factory;

import com.afdor.rws.annotation.extension.Activation;
import com.afdor.rws.annotation.extension.Extension;
import com.afdor.rws.core.extension.ExtensionFactory;
import com.afdor.rws.core.extension.ExtensionItem;
import com.afdor.rws.core.extension.ExtensionLoader;
import com.afdor.rws.core.extension.utils.ExtensionUtils;
import com.afdor.rws.core.utils.Holder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.regex.Pattern;

/**
 * 默认扩展点加载实现，基于配置文件实现
 *
 * @author 悭梵
 * @date Created in 2018-09-07 10:21
 */
@Slf4j
@ToString
@EqualsAndHashCode
@Extension(value = "default", order = Extension.MAX_ORDER, interfaceClass = ExtensionFactory.class)
public class DefaultExtensionFactory implements ExtensionFactory {
    /**
     * 缓存指定名称对应扩展点的类
     */
    protected static final ConcurrentMap<Class<?>, ConcurrentMap<String, Class<?>>> CACHED_INTERFACE_EXTENSION_CLASSES = new ConcurrentHashMap<Class<?>, ConcurrentMap<String, Class<?>>>(128);
    /**
     * 缓存-单例对象实例化按名称存储
     */
    protected static final ConcurrentMap<Class<?>, ConcurrentMap<String, Holder<?>>> SINGLETON_INSTANCE_MUTIL_INTERFACE_MAP = new ConcurrentHashMap<Class<?>, ConcurrentMap<String, Holder<?>>>(128);

    @Getter
    private final ExtensionClassesLoader extensionClassesLoader;
    private final AtomicMarkableReference<List<ExtensionFactory>> factoriesReference = new AtomicMarkableReference<>(null, false);

    public DefaultExtensionFactory() {
        extensionClassesLoader = new DefaultExtensionClassesLoader();
    }

    protected DefaultExtensionFactory(ExtensionClassesLoader extensionClassesLoader) {
        this.extensionClassesLoader = extensionClassesLoader;
    }

    @Override
    public <T> boolean hasExtension(final Class<T> interfaceClass, final String name) {
        // 自身业务
        String fullExtensionName = ExtensionUtils.getFullExtension(interfaceClass, name);
        ConcurrentMap<String, Class<?>> interfaceExtensionClassMap = CACHED_INTERFACE_EXTENSION_CLASSES.get(interfaceClass);
        if (interfaceExtensionClassMap != null && interfaceExtensionClassMap.containsKey(fullExtensionName)) {
            return true;
        }

        // 下级业务
        if (!Objects.equals(ExtensionFactory.class, interfaceClass)) {
            loaderSubFactory();

            if (factoriesReference.getReference() != null) {
                boolean exists = false;
                for (ExtensionFactory factory : factoriesReference.getReference()) {
                    exists = factory.hasExtension(interfaceClass, fullExtensionName);
                    if (exists) {
                        return exists;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public <T> T newExtension(final Class<T> interfaceClass, final String name, final Object... args) {
        // 自身业务
        T extension = null;
        String fullExtensionName = ExtensionUtils.getFullExtension(interfaceClass, name);
        ConcurrentMap<String, Class<?>> interfaceExtensionClassMap = CACHED_INTERFACE_EXTENSION_CLASSES.get(interfaceClass);
        if (interfaceExtensionClassMap != null && interfaceExtensionClassMap.containsKey(fullExtensionName)) {
            extension = createNewExtension(interfaceClass, fullExtensionName, (Class<T>) interfaceExtensionClassMap.get(fullExtensionName), args);
            if (extension != null) {
                return extension;
            }
        }

        // 下级业务
        if (!Objects.equals(ExtensionFactory.class, interfaceClass)) {
            loaderSubFactory();

            if (factoriesReference.getReference() != null) {
                for (ExtensionFactory factory : factoriesReference.getReference()) {
                    extension = factory.newExtension(interfaceClass, name);
                    if (extension != null) {
                        return extension;
                    }
                }
            }
        }
        return null;
    }

    @Override
    public <T> T getExtension(final Class<T> interfaceClass, final String name) {
        // 自身业务
        T extension = null;
        String fullExtensionName = ExtensionUtils.getFullExtension(interfaceClass, name);
        ConcurrentMap<String, Class<?>> interfaceExtensionClassMap = CACHED_INTERFACE_EXTENSION_CLASSES.get(interfaceClass);
        if (interfaceExtensionClassMap != null && interfaceExtensionClassMap.containsKey(fullExtensionName)) {
            extension = createExtension(interfaceClass, fullExtensionName);
            if (extension != null) {
                return extension;
            }
        }

        // 下级业务
        if (!Objects.equals(ExtensionFactory.class, interfaceClass)) {
            loaderSubFactory();

            if (factoriesReference.getReference() != null) {
                for (ExtensionFactory factory : factoriesReference.getReference()) {
                    extension = factory.getExtension(interfaceClass, name);
                    if (extension != null) {
                        return extension;
                    }
                }
            }
        }
        return null;
    }

    @Override
    public <T> Collection<ExtensionItem<T>> loadExtensionClasses(final Class<T> interfaceClass) {
        // 自身业务
        Set<ExtensionItem<T>> extensionItemList = new HashSet<>(32);
        ConcurrentMap<String, Class<?>> interfaceClassMap = CACHED_INTERFACE_EXTENSION_CLASSES.get(interfaceClass);
        if (interfaceClassMap == null) {
            CACHED_INTERFACE_EXTENSION_CLASSES.putIfAbsent(interfaceClass, new ConcurrentHashMap<String, Class<?>>(32));
            interfaceClassMap = CACHED_INTERFACE_EXTENSION_CLASSES.get(interfaceClass);
        }
        ConcurrentMap<String, Class<?>> finalInterfaceClassMap = interfaceClassMap;
        Optional.ofNullable(extensionClassesLoader.loadExtensionClasses(interfaceClass)).orElse(Collections.emptySet()).forEach(item -> {
            extensionItemList.add(item);
            finalInterfaceClassMap.putIfAbsent(item.getName(), item.getExtensionClass());
        });

        // 下级业务
        if (!Objects.equals(ExtensionFactory.class, interfaceClass)) {
            loaderSubFactory();

            if (factoriesReference.getReference() != null) {
                for (ExtensionFactory factory : factoriesReference.getReference()) {
                    extensionItemList.addAll(Optional.ofNullable(factory.loadExtensionClasses(interfaceClass)).orElse(Collections.emptyList()));
                }
            }
        }
        return extensionItemList;
    }

    // -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------    私有方法区域   ---------------------------------------------------------------------------------------
    // -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    /**
     * 加载扩展点管理工厂
     */
    private void loaderSubFactory() {
        if (factoriesReference.getReference() == null) {

            List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
            ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getInstance(ExtensionFactory.class);
            String defaultExtensionFactoryName = ExtensionUtils.getFullExtension(ExtensionFactory.class, "default");
            for (String extensionName : loader.getSupportedExtensions()) {
                if (!defaultExtensionFactoryName.equals(extensionName)) {
                    list.add(loader.getExtension(extensionName));
                }
            }

            if (list.size() > 0) {
                ExtensionUtils.orderExtensionByInstance(list);
                factoriesReference.compareAndSet(null, Collections.unmodifiableList(list), false, true);
            }
        }
    }

    // -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------  创建扩展点实例   ---------------------------------------------------------------------------------------
    // -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    /**
     * 根据名称创建扩展服务实例
     *
     * @param name
     * @return
     */
    protected <T> T createExtension(final Class<T> interfaceClass, final String name, final Object... args) {
        ConcurrentMap<String, Class<?>> interfaceExtensionClassMap = CACHED_INTERFACE_EXTENSION_CLASSES.get(interfaceClass);
        if (interfaceExtensionClassMap != null) {
            Class<T> clazz = (Class<T>) interfaceExtensionClassMap.get(ExtensionUtils.getFullExtension(interfaceClass, name));
            if (clazz != null) {
                Extension extension = clazz.getAnnotation(Extension.class);
                if (extension == null) {
                    throw new IllegalStateException("class " + clazz.getName() + " not has " + Extension.class.getName() + " annotation!");
                } else {
                    Extension.Scope scope = extension.scope();
                    switch (scope) {
                        case PROTOTYPE:
                            return createNewExtension(interfaceClass, name, clazz, args);
                        case SINGLETON:
                            return createSingletonExtension(interfaceClass, name, clazz, args);
                        default:
                            throw new IllegalStateException("unknown extension scope");
                    }
                }
            }
        }
        return null;
    }


    /**
     * 根据类名及方法，创建一个扩展点实例
     *
     * @param name
     * @param clazz
     * @return
     */
    protected <T> T createNewExtension(final Class<T> interfaceClass, final String name, final Class<T> clazz, Object... args) {
        try {
            args = ArrayUtils.nullToEmpty(args);
            if (args == null || args.length == 0) {
                return injectExtension(interfaceClass, new Holder<T>(clazz.newInstance())).get();
            } else {
                Class[] argsClasses = ClassUtils.toClass(args);
                Constructor<T> constructor = ExtensionUtils.getExtensionConstructorByParameters(clazz, argsClasses);
                if (constructor == null) {
                    throw new NullPointerException("not found (name: " + name + ", class: " + interfaceClass.getName() + ") constructor( " + StringUtils.join(argsClasses) + ")");
                }
                return injectExtension(interfaceClass, new Holder<T>(constructor.newInstance(args))).get();
            }
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " + interfaceClass.getName() + ")  could not be instantiated", e);
        }
    }

    /**
     * 根据类名及方法，获取或创建一个单例的扩展点
     *
     * @param name
     * @param clazz
     * @return
     */
    protected <T> T createSingletonExtension(final Class<T> interfaceClass, final String name, Class<T> clazz, Object... args) {
        try {
            Holder<T> holder = null;
            ConcurrentMap<String, Holder<?>> interfaceSingletonInstanceMap = SINGLETON_INSTANCE_MUTIL_INTERFACE_MAP.get(interfaceClass);
            if (interfaceSingletonInstanceMap == null) {
                SINGLETON_INSTANCE_MUTIL_INTERFACE_MAP.putIfAbsent(interfaceClass, new ConcurrentHashMap<String, Holder<?>>(32));
                interfaceSingletonInstanceMap = SINGLETON_INSTANCE_MUTIL_INTERFACE_MAP.get(interfaceClass);
            } else {
                holder = (Holder<T>) interfaceSingletonInstanceMap.get(name);
            }
            if (holder == null) {
                interfaceSingletonInstanceMap.putIfAbsent(name, new Holder<T>());
                holder = (Holder<T>) interfaceSingletonInstanceMap.get(name);
            }
            if (holder.get() == null) {
                synchronized (holder) {
                    if (holder.get() == null) {
                        args = ArrayUtils.nullToEmpty(args);
                        if (args == null || args.length == 0) {
                            holder.set(clazz.newInstance());
                        } else {
                            Class[] argsClasses = ClassUtils.toClass(args);
                            Constructor<T> constructor = ExtensionUtils.getExtensionConstructorByParameters(clazz, argsClasses);
                            if (constructor == null) {
                                throw new NullPointerException("not found (name: " + name + ", clazz: " + clazz.getName() + ") constructor( " + StringUtils.join(argsClasses) + ")");
                            }
                            holder.set(constructor.newInstance(args));
                        }
                    }
                    return injectExtension(interfaceClass, holder).get();
                }
            }
            return holder.get();
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " + clazz.getName() + ")  could not be instantiated", e);
        }
    }

    /**
     * 实例化依赖关系
     *
     * @param holder
     * @return
     */
    protected <T> Holder<T> injectExtension(final Class<T> interfaceClass, final Holder<T> holder) {
        T instance = holder.get();
        Class<?> instanceClass = instance.getClass();

        // 根据set方法实例化依赖关系
        String methodName;
        for (Method method : instanceClass.getMethods()) {
            methodName = method.getName();
            if (// 非静态方法
                    !Modifier.isStatic(method.getModifiers()) &&
                            // 公共方法
                            Modifier.isPublic(method.getModifiers())
                            && ((
                            // 包含激活点注解
                            method.isAnnotationPresent(Activation.class)
                                    // 一个参数的Set方法
                                    && method.getParameterTypes().length == 1
                                    && StringUtils.startsWith(methodName, "set"))
                            || (1 != 1)
                    )) {
                Class<?> parameterType = method.getParameterTypes()[0];
                Activation activation = method.getAnnotation(Activation.class);
                Class<?> parameterClass = activation.interfaceClass();
                try {
                    if (Objects.equals(Object.class, parameterClass)) {
                        if (parameterType.isInterface()) {
                            parameterClass = parameterType;
                        } else if (parameterType.getInterfaces().length > 0) {
                            parameterClass = parameterType.getInterfaces()[0];
                        } else {
                            // 未找到扩展点接口类
                            continue;
                        }
                    }
                    Object object = ExtensionLoader
                            .getInstance(parameterClass)
                            .getExtensionOrDefault(activation.value());
                    if (object != null) {
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                    log.error("fail to inject via method " + method.getName() + " of interface " + parameterClass.getName() + ": " + e.getMessage(), e);
                }
            }
        }
        methodName = null;

        String fieldName;

        for (Field field : FieldUtils.getAllFieldsList(instanceClass)) {
            fieldName = field.getName();
            if (!Modifier.isStatic(field.getModifiers()) && field.isAnnotationPresent(Activation.class)) {
                Activation activation = field.getAnnotation(Activation.class);
                Class<?> fieldClass = activation.interfaceClass();
                try {
                    if (Objects.equals(Object.class, fieldClass)) {
                        if (field.getType().isInterface()) {
                            fieldClass = field.getType();
                        } else if (field.getType().getInterfaces().length > 0) {
                            fieldClass = field.getType().getInterfaces()[0];
                        } else {
                            // 未找到扩展点接口类
                            continue;
                        }
                    }

                    field.setAccessible(true);
                    if (field.get(instance) == null) {
                        Object object = ExtensionLoader
                                .getInstance(fieldClass)
                                .getExtensionOrDefault(field.getAnnotation(Activation.class).value());

                        if (object != null) {
                            field.set(instance, object);
                        }
                    }
                } catch (Exception e) {
                    log.error("fail to inject via method " + fieldName + " of interface " + fieldClass.getName() + ": " + e.getMessage(), e);
                }
            }
        }
        return holder;
    }

    /**
     * 基于文件配置的ExtensionClassesLoader
     */
    @Slf4j
    static class DefaultExtensionClassesLoader implements ExtensionClassesLoader {
        /**
         * 扩展服务名称分割正则表达式
         */
        private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
        /**
         * 框架服务配置路径
         */
        private static final String RWS_DIRECTORY = "META-INF/rws/";
        /**
         * 自定义服务配置路径
         */
        private static final String SERVICES_DIRECTORY = "META-INF/services/";
        /**
         * 内置默认服务配置路径
         */
        private static final String RWS_INTERNAL_DIRECTORY = RWS_DIRECTORY + "internal/";

        /**
         * 缓存指定名称对应扩展点的类
         */
        private static final ConcurrentMap<Class<?>, ConcurrentMap<Class<?>, ExtensionItem<?>>> CACHED_EXTENSION_CLASSES = new ConcurrentHashMap<Class<?>, ConcurrentMap<Class<?>, ExtensionItem<?>>>(16);

        @Override
        public <T> Collection<ExtensionItem<T>> loadExtensionClasses(Class<T> interfaceClass) {
            ConcurrentMap<Class<?>, ExtensionItem<?>> extensionItemMap = CACHED_EXTENSION_CLASSES.get(interfaceClass);
            if (extensionItemMap == null) {
                CACHED_EXTENSION_CLASSES.putIfAbsent(interfaceClass, new ConcurrentHashMap(16));
                extensionItemMap = CACHED_EXTENSION_CLASSES.get(interfaceClass);
            }
            loadDirectory(RWS_INTERNAL_DIRECTORY, interfaceClass);
            loadDirectory(RWS_DIRECTORY, interfaceClass);
            loadDirectory(SERVICES_DIRECTORY, interfaceClass);
            return (Collection) extensionItemMap.values();
        }

        /**
         * 遍历目录下的文件，加载扩展服务
         *
         * @param dir
         * @param interfaceClass
         */
        private <T> void loadDirectory(String dir, Class<T> interfaceClass) {
            String typeClassName = interfaceClass.getName();
            String fileName = dir + typeClassName;
            try {
                Enumeration<URL> urls;
                ClassLoader classLoader = this.getClass().getClassLoader();
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
                if (urls != null) {
                    while (urls.hasMoreElements()) {
                        loadResource(classLoader, urls.nextElement(), interfaceClass);
                    }
                }
            } catch (Throwable e) {
                log.error("Exception when load extension class(interface: " + typeClassName + ", description file: " + fileName + ").", e);
            }
        }

        /**
         * 加载指定文件里的class
         *
         * @param classLoader
         * @param resourceURL
         */
        private <T> void loadResource(ClassLoader classLoader, URL resourceURL, Class<T> interfaceClass) throws Exception {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = StringUtils.trim(line);
                    if (line.length() > 0) {
                        String name = null;
                        int i = line.indexOf('=');
                        if (i > 0) {
                            name = StringUtils.trim(line.substring(0, i));
                            line = StringUtils.trim(line.substring(i + 1));
                        }
                        if (line.length() > 0) {
                            loadClass(name, resourceURL, (Class<T>) Class.forName(line, true, classLoader), interfaceClass);
                        }
                    }
                }
            }
        }

        private <T> void loadClass(String name, URL resourceURL, Class<T> clazz, Class<T> interfaceClass) {
            if (!interfaceClass.isAssignableFrom(clazz)) {
                throw new IllegalStateException("Error when load extension class(interface: " + interfaceClass.getName() + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
            }
            // class必须存在无参的构造方法
            // clazz.getConstructor();
            if (name == null || name.length() == 0) {
                name = findAnnotationName(clazz, interfaceClass);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 可能存在多名称配置
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                for (String n : names) {
                    CACHED_EXTENSION_CLASSES
                            .get(interfaceClass)
                            .putIfAbsent(clazz, ExtensionItem
                                    .builder()
                                    .name(ExtensionUtils.getFullExtension(interfaceClass, n))
                                    .extensionClass((Class) clazz)
                                    .build());
                }
            }
        }

        /**
         * 通过注解获取名称
         *
         * @param clazz
         * @return
         */
        private <T> String findAnnotationName(Class<?> clazz, Class<T> interfaceClass) {
            if (interfaceClass.isAnnotationPresent(Extension.class)) {
                Extension extension = clazz.getAnnotation(Extension.class);
                if (extension == null || StringUtils.isBlank(extension.value())) {
                    String name = clazz.getSimpleName();
                    String typeName = interfaceClass.getSimpleName();
                    if (StringUtils.contains(name, typeName)) {
                        name = StringUtils.substring(name, 0, StringUtils.indexOf(name, typeName));
                    }
                    return name.toLowerCase();
                }
                return extension.value();
            } else {
                throw new IllegalStateException("No such Extension annotation for the class " + clazz.getName());
            }
        }
    }
}
